/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.projectdarkstar.ai.tree;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.Task;
import java.io.Serializable;
import java.util.Arrays;

/**
 * This static class is used to start a parallel tree evaluation.
 * The root of the tree is represented by an instance of a class that
 * implements the TreeNode interface and is Serializable.  The root
 * will then provide futher instances of the TreeNode interface as children
 * and so on down the tree.  Converting recursive algorithyms should be very
 * straight forward with each node creating its children according to the
 * recursion required.
 * <p>
 * When a TreeNode is started, its preDescentEvaluate method is called.
 * It is then asked to provide its children. 
 * If the child has children (is not a  leaf) then the node's childDone method
 * will be called as each child finishes.  When all children have finsihed,
 * its postDescentEvaluate method will be called.
 * <p>
 * <b>VERY IMPORTANT:</b>  The children provided by the TreeNode should be thought of 
 * as immutables.  References should not be kept from children to parents or 
 * vice versa.  This is because  the Java Objects will be attached as state to 
 * an internal ManagedObject and the original objects destroyed as part of 
 * processing. They will be recreated later when the internal ManagedObject 
 * gets executed in its own task.  The <b>ONLY</b> way to access the final calculated 
 * state of a child  obejct is through its own postDescentEvaluate method or 
 * through the childDone() method of its parent.
 * 
 * @author Jeff Kesselman
 */
public class ParallelTreeEvaluator implements Serializable {

    private ParallelTreeEvaluator() {

    }

    /**
     * Starts the parallel evaulation of a tree
     * @param node
     */
    public static void evaluate(ParallelTreeNode node) {
        AppContext.getTaskManager().scheduleTask(
                new NodeEvaluationTask(node));

    }

    private static class NodeEvaluationTask implements Task, Serializable,
            ManagedObject {

        ParallelTreeNode node;
        int id;
        boolean[] childDone;
        ManagedReference parentRef;

        public NodeEvaluationTask(ParallelTreeNode node) {
            this(node, 0);
        }

        private NodeEvaluationTask(ParallelTreeNode node, int id) {
            this(null, node, id);
        }

        private NodeEvaluationTask(NodeEvaluationTask parent, ParallelTreeNode node, int id) {
            this.node = node;
            this.id = id;
            if (parent != null){
                this.parentRef =
                    AppContext.getDataManager().createReference(parent);
            }
        }

        public void run() throws Exception {
            node.preDescentEvaluate();
            if (node.childCount() == 0) { // leaf
                node.postDescentEvaluate(isRoot());
                if (!isRoot()) {
                    parentRef.get(NodeEvaluationTask.class).childDone(this);
                }
                AppContext.getDataManager().removeObject(this);
            } else {
                childDone = new boolean[node.childCount()];
                Arrays.fill(childDone, false);
                int i = 0;
                for (ParallelTreeNode child : node.children()) {
                    AppContext.getTaskManager().scheduleTask(
                            new NodeEvaluationTask(this, child, i++));
                }
            }
        }

        private boolean allChildrenDone() {
            for (boolean b : childDone) {
                if (!b) {
                    return false;
                }
            }
            return true;
        }

        private boolean isRoot() {
            return parentRef == null;
        }

        private void childDone(NodeEvaluationTask child) {
            childDone[child.id] = true;
            node.childDone(child.node);
            if (allChildrenDone()) {
                node.postDescentEvaluate(isRoot());
                if (!isRoot()) {
                    parentRef.get(NodeEvaluationTask.class).childDone(this);
                }                
            }
            
        }
    }
}
